{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "6bYaCABobL5q"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "form",
        "colab": {},
        "colab_type": "code",
        "id": "FlUw7tSKbtg4"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "xc1srSc51n_4"
      },
      "source": [
        "# SavedModel 포맷 사용하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "-nBUqG2rchGH"
      },
      "source": [
        "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://www.tensorflow.org/guide/saved_model\"\u003e\n",
        "    \u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003e\n",
        "    TensorFlow.org에서 보기\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/ko/guide/saved_model.ipynb\"\u003e\n",
        "    \u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003e\n",
        "    구글 코랩(Colab)에서 실행하기\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/ko/guide/saved_model.ipynb\"\u003e\n",
        "    \u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003e\n",
        "    깃허브(GitHub) 소스 보기\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "\u003c/table\u003e"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "collapsed": false,
        "id": "eHp7U1y1p51z"
      },
      "source": [
        "Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도 불구하고 [공식 영문 문서](https://github.com/tensorflow/docs/blob/master/site/en/guide/effective_tf2.md)의 내용과 일치하지 않을 수 있습니다. 이 번역에 개선할 부분이 있다면 [tensorflow/docs](https://github.com/tensorflow/docs) 깃헙 저장소로 풀 리퀘스트를 보내주시기 바랍니다. 문서 번역이나 리뷰에 참여하려면 [docs-ko@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ko)로 메일을 보내주시기 바랍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "CPE-fshLTsXU"
      },
      "source": [
        "SavedModel에는 가중치 및 연산을 포함한 완전한 텐서플로 프로그램이 포함됩니다. 기존에 설계했던 모델 코드를 실행할 필요가 없어 공유하거나 ([TFLite](https://tensorflow.org/lite), [TensorFlow.js](https://js.tensorflow.org/), [TensorFlow Serving](https://www.tensorflow.org/tfx/serving/tutorials/Serving_REST_simple), [TFHub](https://tensorflow.org/hub)와 같은 환경으로) 배포하는 데 유용합니다.\n",
        "\n",
        "파이썬 모델 코드를 가지고 있고 파이썬 내에서 가중치를 불러오고 싶다면, [체크포인트 훈련 가이드](./checkpoint.ipynb)를 참조하세요.\n",
        "\n",
        "빠른 소개를 위해 이 섹션에서는 미리 훈련된 케라스 모델을 내보내고 그 모델로 이미지 분류 요청을 처리합니다. 나머지 가이드에서는 세부 정보와 SavedModel을 만드는 다른 방법에 대해 설명합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "Le5OB-fBHHW7"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "try:\n",
        "  # Colab only\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "    pass\n",
        "import tensorflow as tf\n",
        "from matplotlib import pyplot as plt\n",
        "import numpy as np"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "SofdPKo0G8Lb"
      },
      "outputs": [],
      "source": [
        "file = tf.keras.utils.get_file(\n",
        "    \"grace_hopper.jpg\",\n",
        "    \"https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg\")\n",
        "img = tf.keras.preprocessing.image.load_img(file, target_size=[224, 224])\n",
        "plt.imshow(img)\n",
        "plt.axis('off')\n",
        "x = tf.keras.preprocessing.image.img_to_array(img)\n",
        "x = tf.keras.applications.mobilenet.preprocess_input(\n",
        "    x[tf.newaxis,...])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "sqVcFL10JkF0"
      },
      "source": [
        "실행 예제로 그레이스 호퍼(Grace Hopper)의 이미지와 사용이 쉬운 케라스 사전 훈련 이미지 분류 모델을 사용할 것입니다. 사용자 정의 모델도 사용할 수 있는데, 자세한 것은 나중에 설명합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "JhVecdzJTsKE"
      },
      "outputs": [],
      "source": [
        "#tf.keras.applications.vgg19.decode_predictions\n",
        "labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')\n",
        "imagenet_labels = np.array(open(labels_path).read().splitlines())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "aEHSYjW6JZHV"
      },
      "outputs": [],
      "source": [
        "pretrained_model = tf.keras.applications.MobileNet()\n",
        "result_before_save = pretrained_model(x)\n",
        "print()\n",
        "\n",
        "decoded = imagenet_labels[np.argsort(result_before_save)[0,::-1][:5]+1]\n",
        "\n",
        "print(\"저장 전 결과:\\n\", decoded)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "r4KIsQDZJ5PS"
      },
      "source": [
        "이 이미지의 가장 가능성 있는 예측은 \"군복\"입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "8nfznDmHCW6F"
      },
      "outputs": [],
      "source": [
        "tf.saved_model.save(pretrained_model, \"/tmp/mobilenet/1/\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "pyX-ETE3wX63"
      },
      "source": [
        "저장 경로의 마지막 경로 요소(여기서는 `1/`)는 모델의 버전 번호인 텐서플로 서빙(TensorFlow Serving) 컨벤션을 따릅니다 - 텐서플로 서빙과 같은 도구가 최신 모델을 구분할 수 있게 합니다.\n",
        "\n",
        "SavedModel은 시그니처(signatures)라 불리는 이름있는 함수를 가집니다. 케라스 모델은 `serving_default` 시그니처 키를 사용하여 정방향 패스(forward pass)를 내보냅니다. [SavedModel 커맨드 라인 인터페이스](#details_of_the_savedmodel_command_line_interface)는 디스크에 저장된 SavedModel을 검사할 때 유용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "djmcTavtIZyT"
      },
      "outputs": [],
      "source": [
        "!saved_model_cli show --dir /tmp/mobilenet/1 --tag_set serve --signature_def serving_default"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "VCZZ8avqLF1g"
      },
      "source": [
        "파이썬에서 `tf.saved_model.load`로 SavedModel을 다시 불러오고 해군대장 호퍼(Admiral Hopper)의 이미지가 어떻게 분류되는지 볼 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "NP2UpVFRV7N_"
      },
      "outputs": [],
      "source": [
        "loaded = tf.saved_model.load(\"/tmp/mobilenet/1/\")\n",
        "print(list(loaded.signatures.keys()))  # [\"serving_default\"]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "K5srGzowfWff"
      },
      "source": [
        "가져온 시그니처는 항상 딕셔너리를 반환합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "ChFLpegYfQGR"
      },
      "outputs": [],
      "source": [
        "infer = loaded.signatures[\"serving_default\"]\n",
        "print(infer.structured_outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "cJYyZnptfuru"
      },
      "source": [
        "SavedModel로부터 추론을 실행하면 처음 모델과 같은 결과를 제공합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "9WjGEaS3XfX7"
      },
      "outputs": [],
      "source": [
        "labeling = infer(tf.constant(x))[pretrained_model.output_names[0]]\n",
        "\n",
        "decoded = imagenet_labels[np.argsort(labeling)[0,::-1][:5]+1]\n",
        "\n",
        "print(\"저장과 불러오기 이후의 결과:\\n\", decoded)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "SJEkdXjTWbtl"
      },
      "source": [
        "## 텐서플로 서빙으로 모델 배포하기\n",
        "\n",
        "SavedModel은 파이썬에서 사용하기에 적합하지만, 일반적으로 프로덕션 환경에서는 추론을 위한 전용 서비스를 사용합니다. 이는 텐서플로 서빙을 사용한 SavedModel로 쉽게 구성할 수 있습니다.\n",
        "\n",
        "`tensorflow_model_server`를 노트북이나 로컬 머신에 설치하는 방법을 포함한 텐서플로 서빙에 대한 자세한 내용은 [TensorFlow Serving REST 튜토리얼](https://github.com/tensorflow/serving/blob/master/tensorflow_serving/g3doc/tutorials/Serving_REST_simple.ipynb)을 참조하십시오. 간단한 예를 들면 앞서 내보낸 `mobilenet` 모델을 배포하기 위해 모델 경로를 SavedModel 디렉토리로 설정합니다:\n",
        "\n",
        "```bash\n",
        "nohup tensorflow_model_server \\\n",
        "  --rest_api_port=8501 \\\n",
        "  --model_name=mobilenet \\\n",
        "  --model_base_path=\"/tmp/mobilenet\" \u003eserver.log 2\u003e\u00261\n",
        "```\n",
        "\n",
        "  이제 요청을 보냅니다.\n",
        "\n",
        "```python\n",
        "!pip install requests\n",
        "import json\n",
        "import numpy\n",
        "import requests\n",
        "data = json.dumps({\"signature_name\": \"serving_default\",\n",
        "                   \"instances\": x.tolist()})\n",
        "headers = {\"content-type\": \"application/json\"}\n",
        "json_response = requests.post('http://localhost:8501/v1/models/mobilenet:predict',\n",
        "                              data=data, headers=headers)\n",
        "predictions = numpy.array(json.loads(json_response.text)[\"predictions\"])\n",
        "```\n",
        "\n",
        "`predictions`의 결과는 파이썬에서와 같습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Bi0ILzu1XdWw"
      },
      "source": [
        "### SavedModel 포맷\n",
        "\n",
        "SavedModel은 변수값과 상수를 포함하고 직렬화된 시그니처와 이를 실행하는 데 필요한 상태를 담은 디렉토리입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "6u3YZuYZXyTO"
      },
      "outputs": [],
      "source": [
        "!ls /tmp/mobilenet/1  # assets\tsaved_model.pb\tvariables"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ple4X5utX8ue"
      },
      "source": [
        "`saved_model.pb` 파일은 각각 하나의 함수로 된 이름있는 시그니처 세트를 포함합니다.\n",
        "\n",
        "SavedModel에는 다중 시그니처 세트(`saved_model_cli`의 `tag_set` 매개변수 값으로 확인된 다중 MetaGraph)를 포함할 수 있지만 이런 경우는 드뭅니다. 다중 시그니처 세트를 작성하는 API에는 [`tf.Estimator.experimental_export_all_saved_models`](https://www.tensorflow.org/api_docs/python/tf/estimator/Estimator#experimental_export_all_saved_models) 및 TensorFlow 1.x의 `tf.saved_model.Builder`가 포함됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "Pus0dOYTYXbI"
      },
      "outputs": [],
      "source": [
        "!saved_model_cli show --dir /tmp/mobilenet/1 --tag_set serve"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "eALHpGvRZOhk"
      },
      "source": [
        "`variables` 디렉토리에는 일반적인 훈련 체크포인트 파일이 있습니다([훈련 체크포인트 가이드](./checkpoint.ipynb) 참조)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "EDYqhDlNZAC2"
      },
      "outputs": [],
      "source": [
        "!ls /tmp/mobilenet/1/variables"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "VKmaZQpHahGh"
      },
      "source": [
        "`assets` 디렉토리에는 텐서플로 그래프(TensorFlow graph)에서 사용되는 파일들, 예를 들어 상수 테이블을 초기화하는 데 사용되는 텍스트 파일들이 있습니다. 이번 예제에서는 사용되지 않습니다.\n",
        "\n",
        "SavedModel은 텐서플로 그래프에서 사용되지 않는 파일을 위해 `assets.extra` 디렉토리를 가질 수 있는데, 예를 들면 사용자가 SavedModel과 함께 사용할 파일입니다. 텐서플로 자체는 이 디렉토리를 사용하지 않습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "zIceoF_CYmaF"
      },
      "source": [
        "### 사용자 정의 모델 내보내기\n",
        "\n",
        "첫 번째 섹션에서는, `tf.saved_model.save`가 `tf.keras.Model` 객체에 대한 시그니처를 자동으로 결정했습니다. 이는 케라스의 `Model` 객체가 내보내기 위한 명시적 메서드와 입력 크기를 가지기 때문에 작동했습니다. `tf.saved_model.save`는 저수준(low-level) 모델 설계 API와도 잘 작동하지만, 모델을 텐서플로 서빙에 배포할 계획이라면 시그니처로 사용할 함수를 지정해야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "6EPvKiqXMm3d"
      },
      "outputs": [],
      "source": [
        "class CustomModule(tf.Module):\n",
        "\n",
        "  def __init__(self):\n",
        "    super(CustomModule, self).__init__()\n",
        "    self.v = tf.Variable(1.)\n",
        "\n",
        "  @tf.function\n",
        "  def __call__(self, x):\n",
        "    return x * self.v\n",
        "\n",
        "  @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])\n",
        "  def mutate(self, new_v):\n",
        "    self.v.assign(new_v)\n",
        "\n",
        "module = CustomModule()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "fUrCTSK2HV2b"
      },
      "source": [
        "이 모듈은 `tf.function` 데코레이터가 적용된 두 메서드를 가지고 있습니다. 이 함수들은 SavedModel에 포함되어 있으므로 `tf.saved_model.load` 함수를 사용하여 파이썬 프로그램에 함께 로드됩니다. 하지만 명시적 선언 없이는 텐서플로 서빙과 같은 시그니처 배포 도구와 `saved_model_cli`가 접근할 수 없습니다.\n",
        "\n",
        "`module.mutate`는 `input_signature`를 가지고 있어서 계산 그래프를 SavedModel에 저장하기 위한 정보가 이미 충분히 있습니다. `__call__`은 시그니처가 없기에 저장하기 전 이 메서드를 호출해야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "85PUO9iWH7xn"
      },
      "outputs": [],
      "source": [
        "module(tf.constant(0.))\n",
        "tf.saved_model.save(module, \"/tmp/module_no_signatures\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "eyWD4wr-Ng7m"
      },
      "source": [
        "`input_signature`가 없는 함수의 경우, 저장 전에 사용된 입력의 크기는 함수가 불려진 이후에 사용될 것입니다. 스칼라값으로 `__call__`을 호출했으므로 스칼라값만 받아들일 것입니다"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "xy7oCex1Ibj1"
      },
      "outputs": [],
      "source": [
        "imported = tf.saved_model.load(\"/tmp/module_no_signatures\")\n",
        "assert 3. == imported(tf.constant(3.)).numpy()\n",
        "imported.mutate(tf.constant(2.))\n",
        "assert 6. == imported(tf.constant(3.)).numpy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "lbLNVfVJOTfb"
      },
      "source": [
        "함수는 벡터와 같은 새로운 형식을 수용하지 않습니다.\n",
        "\n",
        "```python\n",
        "imported(tf.constant([3.]))\n",
        "```\n",
        "\n",
        "\u003cpre\u003e\n",
        "ValueError: Could not find matching function to call for canonicalized inputs ((\u003ctf.Tensor 'args_0:0' shape=(1,) dtype=float32\u003e,), {}). Only existing signatures are [((TensorSpec(shape=(), dtype=tf.float32, name=u'x'),), {})].\n",
        "\u003c/pre\u003e"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "WrJqD-epPGnr"
      },
      "source": [
        "`get_concrete_function`을 사용해 입력 크기를 함수 호출 없이 추가할 수 있습니다. 이 함수는 매개변수 값으로 `Tensor` 대신 입력 크기와 데이터 타입을 나타내는 `tf.TensorSpec` 객체를 받습니다. 크기가 `None`이면 모든 크기가 수용 가능합니다. 또는 각 축의 크기(axis size)를 담은 리스트일 수도 있습니다. 축 크기가 'None'이면 그 축에 대해 임의의 크기를 사용할 수 있습니다. 또한 `tf.TensorSpecs`는 이름을 가질 수 있는데, 기본값은 함수의 매개변수 키워드(여기서는 \"x\")입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "1m9Okb75PFmb"
      },
      "outputs": [],
      "source": [
        "module.__call__.get_concrete_function(x=tf.TensorSpec([None], tf.float32))\n",
        "tf.saved_model.save(module, \"/tmp/module_no_signatures\")\n",
        "imported = tf.saved_model.load(\"/tmp/module_no_signatures\")\n",
        "assert [3.] == imported(tf.constant([3.])).numpy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "gvy3GFl4IfSW"
      },
      "source": [
        "`tf.keras.Model`과 `tf.Module`과 같은 객체에 포함된 함수와 변수는 가져올 때 사용할 수 있지만 많은 파이썬의 타입과 속성은 잃어버립니다. 파이썬 프로그램 자체는 SavedModel에 저장되지 않습니다.\n",
        "\n",
        "내보낼 함수를 시그니처로 지정하지 못했기에 시그니처는 없습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "uNTV6o_TIeRu"
      },
      "outputs": [],
      "source": [
        "!saved_model_cli show --dir /tmp/module_no_signatures --tag_set serve"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "BiNtaMZSI8Tb"
      },
      "source": [
        "## 내보낼 시그니처 지정하기\n",
        "\n",
        "어떤 함수가 시그니처라는 것을 나타내려면 저장할 때 `signatures` 매개변수를 지정합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "_pAdgIORR2yH"
      },
      "outputs": [],
      "source": [
        "call = module.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))\n",
        "tf.saved_model.save(module, \"/tmp/module_with_signature\", signatures=call)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "lHiBm-kdKBmG"
      },
      "source": [
        "먼저 `tf.function` 객체를 `get_concrete_function` 메서드를 사용해 `ConcreteFunction` 객체로 바꾸었습니다. 이것은 함수가 고정된 `input_signature` 없이 만들어지고 함수와 연관된 명시적인 `Tensor` 입력이 없었으므로 필수적입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "nAzRHR0UT4hv"
      },
      "outputs": [],
      "source": [
        "!saved_model_cli show --dir /tmp/module_with_signature --tag_set serve --signature_def serving_default"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "0B25WsscTZoC"
      },
      "outputs": [],
      "source": [
        "imported = tf.saved_model.load(\"/tmp/module_with_signature\")\n",
        "signature = imported.signatures[\"serving_default\"]\n",
        "assert [3.] == signature(x=tf.constant([3.]))[\"output_0\"].numpy()\n",
        "imported.mutate(tf.constant(2.))\n",
        "assert [6.] == signature(x=tf.constant([3.]))[\"output_0\"].numpy()\n",
        "assert 2. == imported.v.numpy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "_gH91j1IR4tq"
      },
      "source": [
        "하나의 시그니처를 내보냈고 키는 기본값인 \"serving_default\"가 됩니다. 여러 시그니처를 내보내려면 딕셔너리로 전달합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "6VYAiQmLUiox"
      },
      "outputs": [],
      "source": [
        "@tf.function(input_signature=[tf.TensorSpec([], tf.string)])\n",
        "def parse_string(string_input):\n",
        "  return imported(tf.strings.to_number(string_input))\n",
        "\n",
        "signatures = {\"serving_default\": parse_string,\n",
        "              \"from_float\": imported.signatures[\"serving_default\"]}\n",
        "\n",
        "tf.saved_model.save(imported, \"/tmp/module_with_multiple_signatures\", signatures)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "8IPx_0RWEx07"
      },
      "outputs": [],
      "source": [
        "!saved_model_cli show --dir /tmp/module_with_multiple_signatures --tag_set serve"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "sRdSPjKFfQpx"
      },
      "source": [
        "`saved_model_cli`는 커맨드 라인에서 SavedModel을 직접 실행할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "hcHmgVytfODo"
      },
      "outputs": [],
      "source": [
        "!saved_model_cli run --dir /tmp/module_with_multiple_signatures --tag_set serve --signature_def serving_default --input_exprs=\"string_input='3.'\"\n",
        "!saved_model_cli run --dir /tmp/module_with_multiple_signatures --tag_set serve --signature_def from_float --input_exprs=\"x=3.\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "WiNhHa_Ne82K"
      },
      "source": [
        "## 가져온 모델 미세 튜닝하기\n",
        "\n",
        "변수 객체가 사용 가능하므로 imported 함수를 통해 역전파할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "mSchcIB2e-n0"
      },
      "outputs": [],
      "source": [
        "optimizer = tf.optimizers.SGD(0.05)\n",
        "\n",
        "def train_step():\n",
        "  with tf.GradientTape() as tape:\n",
        "    loss = (10. - imported(tf.constant(2.))) ** 2\n",
        "  variables = tape.watched_variables()\n",
        "  grads = tape.gradient(loss, variables)\n",
        "  optimizer.apply_gradients(zip(grads, variables))\n",
        "  return loss"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "Yx9bO2taJJxm"
      },
      "outputs": [],
      "source": [
        "for _ in range(10):\n",
        "  # \"v\"는 5로 수렴, \"loss\"는 0으로 수렴\n",
        "  print(\"loss={:.2f} v={:.2f}\".format(train_step(), imported.v.numpy()))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qyL9tOPrg5Zw"
      },
      "source": [
        "## SavedModel의 제어 흐름\n",
        "\n",
        "`tf.function`에 들어갈 수 있는 것은 모두 SavedModel에 들어갈 수 있습니다. [AutoGraph](./function.ipynb)를 사용하면 Tensor에 의존하는 조건부 논리를 파이썬 제어 흐름으로 표현할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "tfbh3uGMgBpH"
      },
      "outputs": [],
      "source": [
        "@tf.function(input_signature=[tf.TensorSpec([], tf.int32)])\n",
        "def control_flow(x):\n",
        "  if x \u003c 0:\n",
        "    tf.print(\"유효하지 않음!\")\n",
        "  else:\n",
        "    tf.print(x % 3)\n",
        "\n",
        "to_export = tf.Module()\n",
        "to_export.control_flow = control_flow\n",
        "tf.saved_model.save(to_export, \"/tmp/control_flow\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "bv4EXevIjHch"
      },
      "outputs": [],
      "source": [
        "imported = tf.saved_model.load(\"/tmp/control_flow\")\n",
        "imported.control_flow(tf.constant(-1))  # 유효하지 않음!\n",
        "imported.control_flow(tf.constant(2))   # 2\n",
        "imported.control_flow(tf.constant(3))   # 0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Dk5wWyuMpuHx"
      },
      "source": [
        "## 추정기(Estimator)의 SavedModel\n",
        "\n",
        "추정기는 [`tf.Estimator.export_saved_model`](https://www.tensorflow.org/api_docs/python/tf/estimator/Estimator#export_saved_model)을 통해 SavedModel을 내보냅니다. 자세한 내용은 [Estimator 가이드](https://www.tensorflow.org/guide/estimator)를 참조하십시오."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "B9KQq5qzpzbK"
      },
      "outputs": [],
      "source": [
        "input_column = tf.feature_column.numeric_column(\"x\")\n",
        "estimator = tf.estimator.LinearClassifier(feature_columns=[input_column])\n",
        "\n",
        "def input_fn():\n",
        "  return tf.data.Dataset.from_tensor_slices(\n",
        "    ({\"x\": [1., 2., 3., 4.]}, [1, 1, 0, 0])).repeat(200).shuffle(64).batch(16)\n",
        "estimator.train(input_fn)\n",
        "\n",
        "serving_input_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(\n",
        "  tf.feature_column.make_parse_example_spec([input_column]))\n",
        "export_path = estimator.export_saved_model(\n",
        "  \"/tmp/from_estimator/\", serving_input_fn)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "XJ4PJ-Cl4060"
      },
      "source": [
        "이 SavedModel은 텐서플로 서빙에 배포하는 데 유용한 직렬화된 `tf.Example` 프로토콜 버퍼를 사용합니다. 그러나 `tf.saved_model.load`로 불러오고 파이썬에서 실행할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "c_BUBBNB1UH9"
      },
      "outputs": [],
      "source": [
        "imported = tf.saved_model.load(export_path)\n",
        "\n",
        "def predict(x):\n",
        "  example = tf.train.Example()\n",
        "  example.features.feature[\"x\"].float_list.value.extend([x])\n",
        "  return imported.signatures[\"predict\"](\n",
        "    examples=tf.constant([example.SerializeToString()]))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "C1ylWZCQ1ahG"
      },
      "outputs": [],
      "source": [
        "print(predict(1.5))\n",
        "print(predict(3.5))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "_IrCCm0-isqA"
      },
      "source": [
        "`tf.estimator.export.build_server_input_receiver_fn`를 사용해 `tf.train.Example`이 아닌 원시 텐서를 가지는 입력 함수를 만들 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Co6fDbzw_UnD"
      },
      "source": [
        "## C++에서 SavedModel 불러오기\n",
        "\n",
        "SavedModel의 C++ 버전 [loader](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/cc/saved_model/loader.h)는 SessionOptions 및 RunOptions을 허용하며 경로에서 SavedModel을 불러오는 API를 제공합니다. 불러 올 그래프와 연관된 태그를 지정해야합니다. 불러온 SavedModel의 버전은 SavedModelBundle이라고 하며 MetaGraphDef와 불러온 세션을 포함합니다.\n",
        "\n",
        "```C++\n",
        "const string export_dir = ...\n",
        "SavedModelBundle bundle;\n",
        "...\n",
        "LoadSavedModel(session_options, run_options, export_dir, {kSavedModelTagTrain},\n",
        "               \u0026bundle);\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "b33KuyEuAO3Z"
      },
      "source": [
        "\u003ca id=saved_model_cli/\u003e\n",
        "\n",
        "## SavedModel 커맨드 라인 인터페이스 세부 사항\n",
        "\n",
        "SavedModel 커맨드 라인 인터페이스(CLI)를 사용하여 SavedModel을 검사하고 실행할 수 있습니다.\n",
        "예를 들어, CLI를 사용하여 모델의 `SignatureDef`를 검사할 수 있습니다.\n",
        "CLI를 사용하면 입력 Tensor 크기 및 데이터 타입이 모델과 일치하는지 신속하게 확인할 수 있습니다.\n",
        "또한 모델을 테스트하려는 경우 다양한 형식(예를 들어, 파이썬 표현식)의 샘플 입력을\n",
        "전달하고 출력을 가져와 CLI를 사용하여 정확성 검사를 수행할 수 있습니다.\n",
        "\n",
        "### SavedModel CLI 설치하기\n",
        "\n",
        "대체로 말하자면 다음 두 가지 방법 중 하나로 텐서플로를 설치할 수 있습니다:\n",
        "\n",
        "*  사전에 빌드된 텐서플로 바이너리로 설치\n",
        "*  소스 코드로 텐서플로 빌드\n",
        "\n",
        "사전에 빌드된 텐서플로 바이너리를 통해 설치한 경우 SavedModel CLI가 이미 \n",
        "시스템 경로 `bin\\saved_model_cli`에 설치되어 있습니다.\n",
        "\n",
        "소스 코드에서 텐서플로를 빌드하는 경우 다음 추가 명령을 실행하여 `saved_model_cli`를 빌드해야 합니다:\n",
        "\n",
        "```\n",
        "$ bazel build tensorflow/python/tools:saved_model_cli\n",
        "```\n",
        "\n",
        "### 명령 개요\n",
        "\n",
        "SavedModel CLI는 SavedModel의 `MetaGraphDef`에 대해 다음 두 명령어를 지원합니다:\n",
        "\n",
        "* SavedModel의 `MetaGraphDef`에 대한 계산을 보여주는 `show`\n",
        "* `MetaGraphDef`에 대한 계산을 실행하는 `run`\n",
        "\n",
        "\n",
        "### `show` 명령어\n",
        "\n",
        "SavedModel은 태그 세트로 식별되는 하나 이상의 `MetaGraphDef`를 포함합니다.\n",
        "모델을 텐서플로 서빙에 배포하려면, 각 모델에 어떤 종류의 `SignatureDef`가 있는지, 그리고 입력과 출력은 무엇인지 궁금할 수 있습니다.\n",
        "`show` 명령은 SavedModel의 내용을 계층적 순서로 검사합니다. 구문은 다음과 같습니다:\n",
        "\n",
        "```\n",
        "usage: saved_model_cli show [-h] --dir DIR [--all]\n",
        "[--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]\n",
        "```\n",
        "\n",
        "예를 들어, 다음 명령은 SavedModel에서 사용 가능한 모든 `MetaGraphDef` 태그 세트를 보여줍니다:\n",
        "\n",
        "```\n",
        "$ saved_model_cli show --dir /tmp/saved_model_dir\n",
        "The given SavedModel contains the following tag-sets:\n",
        "serve\n",
        "serve, gpu\n",
        "```\n",
        "\n",
        "다음 명령은 `MetaGraphDef`에서 사용 가능한 모든 `SignatureDef` 키를 보여줍니다:\n",
        "\n",
        "```\n",
        "$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve\n",
        "The given SavedModel `MetaGraphDef` contains `SignatureDefs` with the\n",
        "following keys:\n",
        "SignatureDef key: \"classify_x2_to_y3\"\n",
        "SignatureDef key: \"classify_x_to_y\"\n",
        "SignatureDef key: \"regress_x2_to_y3\"\n",
        "SignatureDef key: \"regress_x_to_y\"\n",
        "SignatureDef key: \"regress_x_to_y2\"\n",
        "SignatureDef key: \"serving_default\"\n",
        "```\n",
        "\n",
        "`MetaGraphDef`가 태그 세트에 *여러 개의* 태그를 가지고 있는 경우, 모든 태그를 지정해야 하며,\n",
        "각 태그는 쉼표로 구분해야 합니다. 예를 들어:\n",
        "\n",
        "\u003cpre\u003e\n",
        "$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve,gpu\n",
        "\u003c/pre\u003e\n",
        "\n",
        "특정 `SignatureDef`에 대한 모든 입력 및 출력 텐서 정보(TensorInfo)를 표시하려면 `SignatureDef` 키를\n",
        "`signature_def` 옵션으로 전달하십시오. 이것은 나중에 계산 그래프를 실행하기 위해 입력 텐서의 텐서 키 값,\n",
        "크기 및 데이터 타입을 알고자 할 때 매우 유용합니다. 예를 들어:\n",
        "\n",
        "```\n",
        "$ saved_model_cli show --dir \\\n",
        "/tmp/saved_model_dir --tag_set serve --signature_def serving_default\n",
        "The given SavedModel SignatureDef contains the following input(s):\n",
        "  inputs['x'] tensor_info:\n",
        "      dtype: DT_FLOAT\n",
        "      shape: (-1, 1)\n",
        "      name: x:0\n",
        "The given SavedModel SignatureDef contains the following output(s):\n",
        "  outputs['y'] tensor_info:\n",
        "      dtype: DT_FLOAT\n",
        "      shape: (-1, 1)\n",
        "      name: y:0\n",
        "Method name is: tensorflow/serving/predict\n",
        "```\n",
        "\n",
        "SavedModel에 사용 가능한 모든 정보를 표시하려면 `--all` 옵션을 사용하십시오. 예를 들어:\n",
        "\n",
        "\u003cpre\u003e\n",
        "$ saved_model_cli show --dir /tmp/saved_model_dir --all\n",
        "MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:\n",
        "\n",
        "signature_def['classify_x2_to_y3']:\n",
        "  The given SavedModel SignatureDef contains the following input(s):\n",
        "    inputs['inputs'] tensor_info:\n",
        "        dtype: DT_FLOAT\n",
        "        shape: (-1, 1)\n",
        "        name: x2:0\n",
        "  The given SavedModel SignatureDef contains the following output(s):\n",
        "    outputs['scores'] tensor_info:\n",
        "        dtype: DT_FLOAT\n",
        "        shape: (-1, 1)\n",
        "        name: y3:0\n",
        "  Method name is: tensorflow/serving/classify\n",
        "\n",
        "...\n",
        "\n",
        "signature_def['serving_default']:\n",
        "  The given SavedModel SignatureDef contains the following input(s):\n",
        "    inputs['x'] tensor_info:\n",
        "        dtype: DT_FLOAT\n",
        "        shape: (-1, 1)\n",
        "        name: x:0\n",
        "  The given SavedModel SignatureDef contains the following output(s):\n",
        "    outputs['y'] tensor_info:\n",
        "        dtype: DT_FLOAT\n",
        "        shape: (-1, 1)\n",
        "        name: y:0\n",
        "  Method name is: tensorflow/serving/predict\n",
        "\u003c/pre\u003e\n",
        "\n",
        "\n",
        "### `run` 명령어\n",
        "\n",
        "`run` 명령을 호출하여 그래프 계산을 실행하고, 입력을 전달한 다음 출력을 표시(하고 선택적으로 저장)합니다.\n",
        "구문은 다음과 같습니다:\n",
        "\n",
        "```\n",
        "usage: saved_model_cli run [-h] --dir DIR --tag_set TAG_SET --signature_def\n",
        "                           SIGNATURE_DEF_KEY [--inputs INPUTS]\n",
        "                           [--input_exprs INPUT_EXPRS]\n",
        "                           [--input_examples INPUT_EXAMPLES] [--outdir OUTDIR]\n",
        "                           [--overwrite] [--tf_debug]\n",
        "```\n",
        "\n",
        "`run` 명령은 입력을 모델에 전달하는 다음 세 가지 방법을 제공합니다:\n",
        "\n",
        "* `--inputs` 옵션을 사용하여 넘파이(numpy) ndarray를 파일에 전달할 수 있습니다.\n",
        "* `--input_exprs` 옵션을 사용하여 파이썬 표현식을 전달할 수 있습니다.\n",
        "* `--input_examples` 옵션을 사용하여 `tf.train.Example`을 전달할 수 있습니다.\n",
        "\n",
        "#### `--inputs`\n",
        "\n",
        "입력 데이터를 파일에 전달하려면, 다음과 같은 일반적인 형식을 가지는 `--inputs` 옵션을 지정합니다:\n",
        "\n",
        "```bsh\n",
        "--inputs \u003cINPUTS\u003e\n",
        "```\n",
        "\n",
        "여기서 *INPUTS*는 다음 형식 중 하나입니다:\n",
        "\n",
        "*  `\u003cinput_key\u003e=\u003cfilename\u003e`\n",
        "*  `\u003cinput_key\u003e=\u003cfilename\u003e[\u003cvariable_name\u003e]`\n",
        "\n",
        "여러 개의 *INPUTS*를 전달할 수 있습니다. 여러 입력을 전달하는 경우 세미콜론을 사용하여 각 *INPUTS*를 구분하십시오.\n",
        "\n",
        "`saved_model_cli`는 `numpy.load`를 사용하여 *filename*을 불러옵니다.\n",
        "*filename*은 다음 형식 중 하나일 수 있습니다:\n",
        "\n",
        "*  `.npy`\n",
        "*  `.npz`\n",
        "*  피클(pickle) 포맷\n",
        "\n",
        "`.npy` 파일은 항상 넘파이 ndarray를 포함합니다. 그러므로 `.npy` 파일에서 불러올 때,\n",
        "배열 내용이 지정된 입력 텐서에 직접 할당될 것입니다. 해당 `.npy` 파일과 함께 *variable_name*을 지정하면\n",
        "*variable_name*이 무시되고 경고가 발생합니다.\n",
        "\n",
        "`.npz`(zip) 파일에서 불러올 때, 입력 텐서 키로 불러올 zip 파일 내의 변수를 *variable_name*으로\n",
        "선택적으로 지정할 수 있습니다. *variable_name*을 지정하지 않으면 SavedModel CLI는 zip 파일에 하나의 파일만\n",
        "포함되어 있는지 확인하고 지정된 입력 텐서 키로 불러옵니다.\n",
        "\n",
        "피클 파일에서 불러올 때, 대괄호 안에 `variable_name`이 지정되지 않았다면, 피클 파일 안에 있는\n",
        "어떤 것이라도 지정된 입력 텐서 키로 전달될 것입니다. 그렇지 않으면, SavedModel CLI는 피클 파일에\n",
        "딕셔너리가 저장되어 있다고 가정하고 *variable_name*에 해당하는 값이 사용됩니다.\n",
        "\n",
        "#### `--input_exprs`\n",
        "\n",
        "파이썬 표현식을 통해 입력을 전달하려면 `--input_exprs` 옵션을 지정하십시오. 이는 데이터 파일이 없어도\n",
        "모델의 `SignatureDef`의 크기 및 데이터 타입과 일치하는 간단한 입력으로 모델의 정확성 검사를 하려는 경우\n",
        "유용할 수 있습니다. 예를 들어:\n",
        "\n",
        "```bsh\n",
        "`\u003cinput_key\u003e=[[1],[2],[3]]`\n",
        "```\n",
        "\n",
        "파이썬 표현식 외에도 넘파이 함수를 전달할 수 있습니다. 예를 들어:\n",
        "\n",
        "```bsh\n",
        "`\u003cinput_key\u003e=np.ones((32,32,3))`\n",
        "```\n",
        "\n",
        "(`numpy` 모듈은 `np`로 이미 사용 가능하다고 가정합니다.)\n",
        "\n",
        "\n",
        "#### `--input_examples`\n",
        "\n",
        "`tf.train.Example`을 입력으로 전달하려면 `--input_examples` 옵션을 지정하십시오. 입력 키마다 딕셔너리의\n",
        "리스트를 받습니다. 각 딕셔너리는 `tf.train.Example`의 인스턴스입니다. 딕셔너리 키는 기능이며 값은 각 기능의\n",
        "값 리스트입니다. 예를 들어:\n",
        "\n",
        "```bsh\n",
        "`\u003cinput_key\u003e=[{\"age\":[22,24],\"education\":[\"BS\",\"MS\"]}]`\n",
        "```\n",
        "\n",
        "#### 출력 저장\n",
        "\n",
        "기본적으로, SavedModel CLI는 출력을 stdout에 기록합니다. `--outdir` 옵션으로 디렉토리를 전달하면,\n",
        "지정된 디렉토리 안에 출력 텐서 키의 이름을 따라 .npy 파일로 출력이 저장됩니다.\n",
        "\n",
        "기존 출력 파일을 덮어 쓰려면 `--overwrite`를 사용하십시오."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "saved_model.ipynb",
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true,
      "version": "0.3.2"
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
